<script setup lang="ts">
import { isBoolean, isObject, omit, toNumber } from 'lodash-es';
import { computed, PropType, provide, reactive, ref, watch, WatchOptions } from 'vue';
import { appConfig } from './config/app';
import { ApiDataType, apiInjectionKey, Data, PageIn, PageOut } from './index';
import { excludeParams, processParams, syncBrowser } from './page/usePage';
import { currentRoute } from './router';


defineOptions({ name: 'Api' })

const props = defineProps({
  browser: { type: Boolean, default: false },
  first: Boolean,
  immediate: { type: Boolean, default: true },
  inject: { type: Boolean, default: true },
  input: { type: Object as PropType<PageIn | Data> },
  inputWatch: { type: [Object, Boolean] as PropType<WatchOptions | boolean>, default: undefined },
  load: {
    type: Function as PropType<(args?: Data | PageOut) => Promise<PageOut | Data[] | Data>>,
  },
  ok: Function,
  okMsg: { type: String, default: '操作成功' },
  rowKey: { type: String, default: appConfig.data.key },
  spin: { type: [Object, Boolean] },
  type: { type: String as PropType<ApiDataType>, default: 'Page' },
  showContent: { type: Boolean, default: true },
})

const emit = defineEmits(['load', 'submit', 'update:input'])

const Spin = appConfig.component.Spin

const input = reactive<PageIn | Data>({ ...props.input } ?? {})
const watchOptions = isBoolean(props.inputWatch) ? undefined : props.inputWatch;
watch(() => props.input, (val) => {
  if (props.inputWatch) {
    try {
      if (JSON.stringify(val) !== JSON.stringify(input)) {
        Object.assign(input, val)
        onLoad()
      }
    } catch {
      Object.assign(input, val)
    }
  } else {
    Object.assign(input, val)
  }
}, watchOptions)

const output = defineModel<PageOut | Data[] | Data | string | number | boolean>('output', {
  type: [Object, Array] as PropType<PageOut | Data[] | Data | string | number | boolean>,
})

if (props.type === 'Page') {
  output.value = { total: 0, [appConfig.page.rows]: [] }
} else if (props.type === 'Object') {
  output.value = {}
} else if (props.type === 'Array') {
  output.value = []
} else if (props.type === 'String') {
  output.value = ''
} else if (props.type === 'Number') {
  output.value = 0
} else if (props.type === 'Boolean') {
  output.value = false
}

if (props.type === 'Page') {
  input.params || (input.params = {})
  input.pageNo || (input.pageNo = 1)
  input.pageSize || (input.pageSize = appConfig.page.defaultPageSize)
  if (props.browser) {
    const route = currentRoute.value
    if (route) {
      const { query } = route
      input.sorts = query.sorts || input.sorts
      input.pageNo = toNumber(query.pageNo || input.pageNo)
      input.pageSize = toNumber(query.pageSize || input.pageSize)
      input.params = Object.assign(input.params ?? {}, processParams(omit(query, excludeParams)))
    } else {
      console.warn('route: 当前路由没有初始化')
    }
  }
}

const loading = defineModel<boolean>('loading', { type: Boolean as PropType<boolean>, default: false })

const initialized = ref(false)

async function onLoad(args: Data = {}) {
  try {
    loading.value = true
    if (props.type === 'Page') {
      if (args?.pageNo) {
        input.pageNo = args?.pageNo
      }
      props.browser && syncBrowser(input as PageIn)
    }
    output.value = await props.load?.({ ...input, ...args })
  } catch (e) {
    if (props.type === 'Page') {
      props.browser && syncBrowser(input as PageIn)
      output.value = { total: 0, [appConfig.page.rows]: [] }
    } else if (props.type === 'Array') {
      output.value = []
    } else if (props.type === 'Object') {
      output.value = {}
    } else if (props.type === 'String') {
      output.value = ''
    } else if (props.type === 'Number') {
      output.value = 0
    } else if (props.type === 'Boolean') {
      output.value = false
    }
    console.error(e)
  } finally {
    initialized.value = true
    loading.value = false
    emit('load', { input, output: output.value })
    emit('update:input', input)
  }
  return output.value
}

function onSort(...args: any) {
  // @ts-ignore
  appConfig.page.onSort(input as PageIn, ...args)
  onLoad()
}

function onOk() {

}

const page = computed(() => {
  if (props.type === 'Page') {
    return {
      current: input.pageNo,
      onChange: (pageNo: number, pageSize: number) => {
        input.pageNo = pageNo
        input.pageSize = pageSize
        onLoad()
      },
      pageNo: input.pageNo,
      pageSize: input.pageSize,
      total: (output.value as PageOut)?.total,
    }
  } else {
    return {}
  }
})

props.immediate && props.load && onLoad()
props.inject && provide(apiInjectionKey, reactive({
  inject: props.inject,
  input,
  loading,
  onLoad,
  onOk,
  onSort,
  output,
  page,
  type: props.type,
}))

defineExpose({
  input,
  onLoad,
  onOk,
  onSort,
  output,
  page,
})
</script>

<template>
<template v-if="showContent">
  <Spin v-if="spin" v-bind="isObject(spin) ? spin : undefined" :spinning="loading" :loading="loading">
    <template v-if="first">
      <slot
        v-if="initialized"
        :input="input"
        :output="output"
        :onSort="onSort"
        :onLoad="onLoad"
        :loading="loading"
        :page="page"
        :rowKey="rowKey"
      />
    </template>
    <slot
      v-else
      :input="input"
      :output="output"
      :onSort="onSort"
      :onLoad="onLoad"
      :loading="loading"
      :page="page"
      :rowKey="rowKey"
    />
  </Spin>
  <template v-else>
    <template v-if="first">
      <slot
        v-if="initialized"
        :input="input"
        :output="output"
        :onSort="onSort"
        :onLoad="onLoad"
        :loading="loading"
        :page="page"
        :rowKey="rowKey"
      />
    </template>
    <slot
      v-else
      :input="input"
      :output="output"
      :onSort="onSort"
      :onLoad="onLoad"
      :loading="loading"
      :page="page"
      :rowKey="rowKey"
    />
  </template>
</template>
<div v-else style="display: flex;align-items: center;justify-content: center;height: 100%">暂无数据</div>
</template>
